﻿// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global

/* WordList.Builder.cs --
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System.Collections.Generic;
using System.Linq;

using AM.Linguistics.Hunspell.Infrastructure;

#endregion

#nullable enable

namespace AM.Linguistics.Hunspell;

public partial class WordList
{
    /// <summary>
    ///
    /// </summary>
    public sealed class Builder
    {
        /// <summary>
        /// Конструктор по умолчанию.
        /// </summary>
        public Builder()
            : this (null, null, null)
        {
            // пустое тело конструктора
        }

        /// <summary>
        /// Конструктор
        /// </summary>
        /// <param name="affix"></param>
        public Builder (AffixConfig affix)
            : this (affix, null, null)
        {
            // пустое тело конструктора
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="affix"></param>
        /// <param name="flagSetDeduper"></param>
        /// <param name="morphSet"></param>
        internal Builder
            (
                AffixConfig? affix,
                Deduper<FlagSet>? flagSetDeduper,
                Deduper<MorphSet>? morphSet
            )
        {
            Affix = affix;
            FlagSetDeduper = flagSetDeduper ?? new Deduper<FlagSet> (FlagSet.DefaultComparer);
            FlagSetDeduper.Add (FlagSet.Empty);
            MorphSetDeduper = morphSet ?? new Deduper<MorphSet> (MorphSet.DefaultComparer);
            MorphSetDeduper.Add (MorphSet.Empty);
            WordEntryDetailDeduper = new Deduper<WordEntryDetail> (EqualityComparer<WordEntryDetail>.Default);
            WordEntryDetailDeduper.Add (WordEntryDetail.Default);
        }

        private Dictionary<string, List<WordEntryDetail>>? EntryDetailsByRoot;

        /// <summary>
        ///
        /// </summary>
        public readonly AffixConfig? Affix;

        /// <summary>
        /// Spelling replacement suggestions based on phonetics.
        /// </summary>
        public List<SingleReplacement>? PhoneticReplacements;

        internal readonly Deduper<FlagSet> FlagSetDeduper;

        internal readonly Deduper<MorphSet> MorphSetDeduper;

        internal readonly Deduper<WordEntryDetail> WordEntryDetailDeduper;

        /// <summary>
        ///
        /// </summary>
        /// <param name="word"></param>
        /// <param name="detail"></param>
        public void Add
            (
                string word,
                WordEntryDetail detail
            )
        {
            var details = GetOrCreateDetailList (word);

            details.Add (detail);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="word"></param>
        /// <returns></returns>
        internal List<WordEntryDetail> GetOrCreateDetailList
            (
                string word
            )
        {
            if (!EntryDetailsByRoot!.TryGetValue (word, out var details))
            {
                details = new List<WordEntryDetail> (2);
                EntryDetailsByRoot.Add (word, details);
            }

            return details;
        }

        /// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        public WordList ToImmutable()
        {
            return ToImmutable (false);
        }

        /// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        public WordList MoveToImmutable()
        {
            return ToImmutable (true);
        }

        private WordList ToImmutable (bool destructive)
        {
            var affix = Affix ?? new AffixConfig.Builder().MoveToImmutable();

            var nGramRestrictedFlags = Dedup (FlagSet.Create (
                new[]
                    {
                        affix.ForbiddenWord,
                        affix.NoSuggest,
                        affix.NoNgramSuggest,
                        affix.OnlyInCompound,
                        SpecialFlags.OnlyUpcaseFlag
                    }
                    .Where (f => f.HasValue)));

            var result = new WordList (affix)
            {
                NGramRestrictedFlags = nGramRestrictedFlags
            };

            if (EntryDetailsByRoot == null)
            {
                result.EntriesByRoot = new Dictionary<string, WordEntryDetail[]>();
            }
            else
            {
                result.EntriesByRoot = new Dictionary<string, WordEntryDetail[]> (EntryDetailsByRoot.Count);
                foreach (var pair in EntryDetailsByRoot)
                {
                    result.EntriesByRoot.Add (pair.Key, pair.Value.ToArray());
                }

                if (destructive)
                {
                    EntryDetailsByRoot = null;
                }
            }

            result.AllReplacements = affix.Replacements;
            if (PhoneticReplacements != null && PhoneticReplacements.Count != 0)
            {
                // store ph: field of a morphological description in reptable
                if (result.AllReplacements!.IsEmpty)
                {
                    result.AllReplacements = SingleReplacementSet.Create (PhoneticReplacements);
                }
                else
                {
                    result.AllReplacements =
                        SingleReplacementSet.Create (result.AllReplacements.Concat (PhoneticReplacements));
                }
            }

            result.NGramRestrictedDetails = new Dictionary<string, WordEntryDetail[]>();

            var details = new List<WordEntryDetail>();
            foreach (var rootSet in result.EntriesByRoot)
            {
                details.Clear();
                foreach (var entry in rootSet.Value)
                {
                    if (nGramRestrictedFlags!.ContainsAny (entry.Flags))
                    {
                        details.Add (entry);
                    }
                }

                if (details.Count != 0)
                {
                    result.NGramRestrictedDetails.Add (rootSet.Key, details.ToArray());
                }
            }

            return result;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="expectedSize"></param>
        public void InitializeEntriesByRoot
            (
                int expectedSize
            )
        {
            if (EntryDetailsByRoot != null)
            {
                return;
            }

            EntryDetailsByRoot = expectedSize <= 0
                ? new Dictionary<string, List<WordEntryDetail>>()

                // PERF: because we add more entries than we are told about, we add a bit more to the expected size
                : new Dictionary<string, List<WordEntryDetail>> (expectedSize / 100 + expectedSize);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public FlagSet? Dedup
            (
                FlagSet? value
            )
        {
            return value is null
                ? value
                : value.Count == 0
                    ? FlagSet.Empty
                    : FlagSetDeduper.GetEqualOrAdd (value);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public MorphSet? Dedup
            (
                MorphSet? value
            )
        {
            return value is null
                ? value
                : value.Count == 0
                    ? MorphSet.Empty
                    : MorphSetDeduper.GetEqualOrAdd (value);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public WordEntryDetail? Dedup
            (
                WordEntryDetail? value
            )
        {
            return value is null
                ? value
                : WordEntryDetailDeduper.GetEqualOrAdd (value);
        }
    }
}
